package jfox.platform.infrastructure;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;

import jfox.platform.aaa.bo.UserBO;
import jfox.platform.aaa.model.UserContext;
import jfox.platform.common.Constants;
import jfox.platform.common.JSONUtils;
import jfox.platform.common.Utils;
import jfox.platform.datadict.bo.DictCategoryBO;
import jfox.platform.datadict.bo.DictItemBO;
import jfox.platform.datadict.entity.DictCategory;
import jfox.platform.datadict.entity.DictItem;
import jfox.platform.datadict.util.DataDicts;
import jfox.platform.function.bo.NodeBO;
import jfox.platform.function.entity.Node;
import jfox.platform.function.exception.NodeNotRegisteredException;
import jfox.platform.logging.bo.LogBO;
import jfox.platform.logging.entity.Log;
import org.apache.velocity.app.FieldMethodizer;
import org.jfox.mvc.ActionContext;
import org.jfox.mvc.ActionSupport;
import org.jfox.mvc.Invocation;
import org.jfox.mvc.PageContext;
import org.jfox.mvc.PermissionNotAllowedException;
import org.jfox.mvc.SessionContext;
import org.jfox.mvc.servlet.ControllerServlet;

/**
 * 做日志记录、权限判断
 * <p/>
 * 所有 Action 从这里继承
 * <p/>
 * //TODO: 支持多语言 successView
 * //TODO: 支持 themes 选择
 * //TODO: 设置通用变量
 * //TODO: 检查新到消息
 *
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */

//TODO: 记录左边树的操作历史，以显示在页面上 

public abstract class SuperAction extends ActionSupport {
    public static final String SESSION_THEME_KEY = "theme";
    public static final String DEFAULT_THEME = "VintageSugar";
    public static final String LANG_KEY = "lang";
    public static final String DEFAULT_LANG = "zh_CN";

    public static final String PAGE_THEME_KEY = "J_THEME";
    public static final String PAGE_JSONUTILS_KEY = "J_JSONUTILS";
    public static final String PAGE_UTILS_KEY = "J_UTILS";
    public static final String PAGE_CONSTANTS_KEY = "J_CONSTANTS";
    public static final String PAGE_MODULES_KEY = "J_MODULES";
    public static final String PAGE_MODULE_KEY = "J_MODULE";
    public static final String PAGE_NODE_KEY = "J_NODE";
    public static final String PAGE_MENU_NODES_KEY = "J_MENU_NODES";
    public static final String PAGE_DATA_DICT_KEY = "J_DATADICT";
    public static final String PAGE_BUTTON_NODE_GROUPS = "J_BUTTON_NODE_GROUPS";

    public static final String JSON_RESPONSE_OBJ = "J_JSON_RESPONSE_OBJ";

    public static final String PAGE_RESPONSE_TIME = "J_RESPONSE_TIME";

    public static final String SESSION_USER_KEY = Constants.SESSION_USER_KEY;

    /**
     * 在 InvocationContext 中保存 START_TIME，以便 postAction 计算时间
     */
    private static final String START_TIME = "__START_TIME__";

    /**
     * 缓存 Module 列表
     */
    private List<Node> moduleNodes = new ArrayList<Node>();

    @EJB
    protected NodeBO nodeBO;

    @EJB
    protected LogBO logBO;

    @EJB
    protected DictCategoryBO dictCategoryBO;

    @EJB
    protected DictItemBO dictItemBO;

    @EJB
    protected UserBO userBO;

    /**
     * 判断是否能够执行该 Action，在 preExecute 之后，preAction 之前执行
     *
     * 超类完成通用权限判断，如果需要更精确的判断，则可以覆盖该办法
     *
     * @param actionContext
     * @return true/false
     */
    protected boolean hasPermission(ActionContext actionContext) throws PermissionNotAllowedException {
        UserContext userContext = getUserContext();

        // 管理员用户拥有所有权限，不作检查
        if(userContext.getUsername().equals(Constants.ADMINISTRATOR_NAME)) {
            return true;
        }

        Node currentNode = (Node)actionContext.getPageContext().getAttribute(PAGE_NODE_KEY);
        //使用 current Node id 判断
        if(userContext.hasPermission(currentNode.getId())) {
           return true;
        }
        else {
            throw new PermissionNotAllowedException(actionContext.getFullActionMethodName(), getUserContext().getUsername());
        }
    }

    protected void preExecute(ActionContext actionContext) {
        super.preExecute(actionContext);
        PageContext pageContext = actionContext.getPageContext();
        // 设置起始时间
        actionContext.setAttribute(START_TIME, System.currentTimeMillis());
        
        pageContext.setAttribute(PAGE_JSONUTILS_KEY, JSONUtils.getInstance());
        // FieldMethodizer 支持静态Field
        pageContext.setAttribute(PAGE_CONSTANTS_KEY, new FieldMethodizer(Constants.getInstance()));
        pageContext.setAttribute(PAGE_UTILS_KEY, Utils.getInstance());
    }

    protected void preAction(ActionContext actionContext) {
        super.preAction(actionContext);

        // 清空 modules 以在Module修改后能刷新模块列表
        moduleNodes.clear();

        actionContext.getSessionContext();

        PageContext pageContext = actionContext.getPageContext();
        // init current Node & Module
        String actionMethodName = actionContext.getFullActionMethodName();
        Node currentModule = null;
        Node currentNode = nodeBO.getNodeByBindAction(actionMethodName);
        if(currentNode == null){
            // 拒绝执行
            throw new NodeNotRegisteredException("BIND_ACTION=" + actionMethodName);
        }
        else if (currentNode.getRootId() == 0) { // 就是 Module
            currentModule = currentNode;
        }
        else {
            currentModule = nodeBO.getNodeById(currentNode.getRootId());
        }

        // 根据上下文获得页面上的按钮，以及左边菜单
        Map<String, List<Node>> nodeGroups = new HashMap<String, List<Node>>();

        List<Node> menuNodes = nodeBO.getMenuNodesByModuleId(currentModule.getId());
        if (menuNodes == null) {
            menuNodes = new ArrayList<Node>();
        }

        // child nodes in current node
        List<Node> childrenNodes = nodeBO.getButtonChildNodes(currentNode.getId());
        if (childrenNodes != null && !childrenNodes.isEmpty()) {
            for (Node node : childrenNodes) {
                String nodeGroup = node.getNodeGroup();
                if (nodeGroup == null || nodeGroup.trim().length() == 0) {
                    nodeGroup = Node.NODE_GORUP_A;
                }
                if (!nodeGroups.containsKey(nodeGroup)) {
                    List<Node> nodes = new ArrayList<Node>();
                    nodeGroups.put(nodeGroup, nodes);
                }
                nodeGroups.get(nodeGroup).add(node);
            }
        }
        // Module Nodes
        pageContext.setAttribute(PAGE_MODULES_KEY, getModuleNodes());
        // current Module
        pageContext.setAttribute(PAGE_MODULE_KEY, currentModule);
        // current Node
        pageContext.setAttribute(PAGE_NODE_KEY, currentNode);
        // Menu Nodes
        pageContext.setAttribute(PAGE_MENU_NODES_KEY, menuNodes);
        //buttonNodeGroups
        pageContext.setAttribute(PAGE_BUTTON_NODE_GROUPS, nodeGroups);
        // 数据字典
        pageContext.setAttribute(PAGE_DATA_DICT_KEY, initDictData());
    }

    protected void postAction(ActionContext actionContext) {
        super.postAction(actionContext);
    }

    protected void postExecute(ActionContext actionContext) {
        super.postExecute(actionContext);
        SessionContext sessionContext = actionContext.getSessionContext();
        PageContext pageContext = actionContext.getPageContext();

        long responseTime = System.currentTimeMillis() - (Long)actionContext.getAttribute(START_TIME);
        pageContext.setAttribute(PAGE_RESPONSE_TIME, responseTime);

        // TODO: set default session

        // 设置主题
        String theme = (String)sessionContext.getAttribute(SESSION_THEME_KEY);
        if (theme == null) {
            theme = DEFAULT_THEME;
            sessionContext.setAttribute(SESSION_THEME_KEY, theme);
        }
        pageContext.setAttribute(PAGE_THEME_KEY, theme);

        sessionContext.setAttribute(LANG_KEY, DEFAULT_LANG);
        if (!pageContext.getTargeView().endsWith(ControllerServlet.getActionSuffix())) { // targetView 是 .do 不需要设置多语言
            // 设置多语言
            String lang = (String)sessionContext.getAttribute(LANG_KEY);
            pageContext.setTargetView(lang + "/" + pageContext.getTargeView());
        }
        else {
            pageContext.setTargetView(pageContext.getTargeView());
        }
        //log action method done
        logSystem();
    }

    protected synchronized List<Node> getModuleNodes() {
        if (moduleNodes.isEmpty()) {
            List<Node> allModules = nodeBO.getModuleNodes();
            moduleNodes.addAll(allModules);
        }
        return moduleNodes;
    }


    protected UserContext getUserContext(){
        SessionContext sessionContext = SessionContext.getCurrentThreadSessionContext();
        if (sessionContext.containsAttribute(SESSION_USER_KEY)) {
            return (UserContext)sessionContext.getAttribute(SESSION_USER_KEY);
        }
        else {
            return new UserContext(userBO.getUnkown(), Collections.EMPTY_LIST, Collections.EMPTY_LIST);
        }
    }

    protected Node getInvocationModule(ActionContext actionContext){
        return (Node)actionContext.getPageContext().getAttribute(PAGE_MODULE_KEY);
    }

    protected Node getInvocationNode(ActionContext actionContext){
        return (Node)actionContext.getPageContext().getAttribute(PAGE_NODE_KEY);
    }

    // --------------   日志记录方法   -------------//
    protected void log(String message){
        log(message, null);
    }

    protected void log(String message, Exception exception){
        ActionContext actionContext = ActionContext.getCurrentThreadInvocationContext();
        Log log = new Log();
        String info = message;
        if(exception != null) {
            info += "\n" + exception.getMessage();
        }
        log.setName(actionContext.getFullActionMethodName());
        log.setDescription(info);
        log.setCreator(getUserContext().getUsername());
        log.setIpAddress(actionContext.getRemoteAddress());
        log.setModuleId(getInvocationModule(actionContext).getId());
        log.setModuleName(getInvocationModule(actionContext).getName());
        log.setNodeId(getInvocationNode(actionContext).getId());
        log.setNodeName(getInvocationNode(actionContext).getName());
        log.setSuccess(exception == null ? Log.SUCCESS_YES : Log.SUCCESS_NO);
        log.setType(Log.TYPE_BUSINESS);
        log.setConsumedMilliSeconds((Long)actionContext.getPageContext().getAttribute(PAGE_RESPONSE_TIME));
        logBO.log(log);
    }

    private void logSystem() {
        ActionContext actionContext = ActionContext.getCurrentThreadInvocationContext();
        PageContext pageContext = actionContext.getPageContext();
        Exception exception = null;
        if(pageContext.hasBusinessException()){
            exception = pageContext.getBusinessException();
        }
        else if(pageContext.hasValidateException()){
            exception = pageContext.getValidateExceptions().values().iterator().next();
        }

        Log log = new Log();
        log.setName(actionContext.getFullActionMethodName());

        Invocation invocation = actionContext.getInvocation();

        String info = invocation != null ? invocation.toString() : "";
        if(exception != null) {
            info += "\n\n" + exception.toString();
        }
        log.setDescription(info);
        log.setCreator(getUserContext().getUsername());
        log.setIpAddress(actionContext.getRemoteAddress());

        Node moduleNode = getInvocationModule(actionContext);
        if(moduleNode != null) {
            log.setModuleId(moduleNode.getId());
            log.setModuleName(moduleNode.getName());
            log.setNodeId(getInvocationNode(actionContext).getId());
            log.setNodeName(getInvocationNode(actionContext).getName());
        }
        else {
            log.setModuleId(0);
            log.setModuleName("UNKOWN");
            log.setNodeId(0);
            log.setNodeName("UNKOWN");
        }

        log.setSuccess(exception == null ? Log.SUCCESS_YES : Log.SUCCESS_NO);
        log.setType(Log.TYPE_SYSTEM);
        log.setConsumedMilliSeconds((Long)actionContext.getPageContext().getAttribute(PAGE_RESPONSE_TIME));
        logBO.log(log);
    }

    /**
     * 装载字典数据，以便缓存
     */
    protected DataDicts initDictData(){
        List<DictCategory> dictCategories = dictCategoryBO.getAllCategory();
        List<DictItem> dictItems = dictItemBO.getAllDictItem();
        return DataDicts.initDataDictHelper(dictCategories, dictItems);
    }

    public static void main(String[] args) {

    }
}
